import 'dva-hooks.dart';
import 'dva-model.dart';
import 'dva-reducer.dart';
import 'redux.dart';

const String SHOW = '@@DVA_LOADING/SHOW';
const String HIDE = '@@DVA_LOADING/HIDE';
const String NAMESPACE = 'loading';

class DvaLoadingOpts {
  const DvaLoadingOpts({
    this.namespace,
    this.only,
    this.except
  });

  final String? namespace;
  final List<String>? only;
  final List<String>? except;
}

class DvaLoadingState extends DvaState {
  bool global = false;
  Map<String, dynamic>? models;
  Map<String, dynamic>? effects;

  @override
  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'global': global,
      'models': models,
      'effects': effects,
    };
  }

  @override
  void fromJson(Map<String, dynamic> json) {
    global = json['global'];
    models = json['models'] == null ? <String, dynamic>{} : json['models'] as Map<String, dynamic>;
    effects = json['effects'] == null ? <String, dynamic>{} : json['effects'] as Map<String, dynamic>;
  }
}

class DvaLoadingReducer extends DvaReducer<DvaLoadingState> {
  DvaLoadingReducer({
    Model<DvaLoadingState>? model, 
    Store? store, 
    UseEffectFunction? useEffect, 
    StateChangeEventFunction? onStateChange
  }) : super(
    model: model,
    store: store,
    useEffect: useEffect,
    onStateChange: onStateChange
  );

  @override
  bool broadcast() {
   if(canUpdate(lastState, nextState)) {
      store!.broadcast(lastState, nextState);
      lastState = nextState.clone();
      return true;
    }
    return false;
  }

  @override
  Future<StoreOfState<DvaLoadingState>> runReducer(StoreOfState<DvaLoadingState> state, DvaAction action) async {
    if(!action.type.contains(SHOW) && !action.type.contains(HIDE)) {
      return state;
    }

    final Map<String, String> payload = action.payload as Map<String, String>;
    final String namespace = payload['namespace']!;
    final String actionType = payload['actionType']!;
    final DvaLoadingState? lastState = state.getState();

    if(lastState != null) {
      if(lastState.models == null) {
        lastState.models = <String, dynamic>{};
      }

      if(lastState.effects == null) {
        lastState.effects = <String, dynamic>{};
      }

      switch (action.type) {
        case SHOW:
          lastState.global = true;
          lastState.models![namespace] = true;
          lastState.effects![actionType] = true;
          state.setState(lastState);
          break;
        case HIDE:
          lastState.effects![actionType] = false;
          lastState.models![namespace] = lastState.effects?.keys.firstWhere((String key){
            return key.indexOf(namespace) == 0;
          }) != null;
          lastState.global = lastState.models?.keys.firstWhere((String _namespace) {
              return lastState.models![_namespace];
            }) != null;
          state.setState(lastState);
          break;
        default:
          break;
      }
    }
    if(broadcast() && onStateChange != null) {
      onStateChange!(nextState, action);
    }
    return state;
  }
}

DvaHook createLoading({ DvaLoadingOpts opts = const DvaLoadingOpts()}) {
  final String namespace = opts.namespace ?? NAMESPACE;
  final DvaLoadingState initialState = DvaLoadingState();

  EffectFunction useEffect(EffectFunction effectFunction) {
    return (DvaAction action, ModelEffects effects) async {
      final String _namespace = action.namespace!;
      final String actionType = action.type;

      if (
        (opts.only?.isNotEmpty != true && opts.except?.isNotEmpty != true) ||
        (opts.only?.isNotEmpty == true && opts.only!.contains(actionType)) ||
        (opts.except?.isNotEmpty == true && opts.except!.contains(actionType))
      ) {
        await effects.dispatchOrigin(DvaAction(SHOW, <String,String>{ 'namespace': _namespace, 'actionType': actionType }));
        await effectFunction(action, effects);
        await effects.dispatchOrigin(DvaAction(HIDE, <String,String>{ 'namespace': _namespace, 'actionType': actionType }));
      } else {
        await effectFunction(action, effects);
      }
    };
  }

  final DvaLoadingReducer dvaLoadingReducer = DvaLoadingReducer()..initState =  StoreOfState<DvaLoadingState>(state: initialState);

  return DvaHook(
    extraReducers: <String, DvaReducer<DvaLoadingState>> {
      namespace: dvaLoadingReducer
    },
    useEffect: useEffect,
  );
}